using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Timers;
using Heightmap_Editor.Menu;
using Heightmap_Editor.Menu.Screens;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

namespace Heightmap_Editor
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class HeightmapEditor : Microsoft.Xna.Framework.Game
    {
        public struct VertexPositionColorNormal
        {
            public Vector3 Position;
            public Color Color;
            public Vector3 Normal;

            public readonly static VertexDeclaration VertexDeclaration = new VertexDeclaration
            (
                new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0),
                new VertexElement(sizeof(float) * 3, VertexElementFormat.Color, VertexElementUsage.Color, 0),
                new VertexElement(sizeof(float) * 3 + 4, VertexElementFormat.Vector3, VertexElementUsage.Normal, 0)
            );
        }

        GraphicsDeviceManager _graphics;
        SpriteBatch _spriteBatch;
        GraphicsDevice device;
        private Texture2D _heightmap;
        private Texture2D _crosshair;
        public ScreenManager _screenManager;

        private KeyboardState _keyboard_old, _keyboard_current;
        private MouseState _mouse_old, _mouse_current;
        private GamePadState _gamepad_old, _gamepad_current;

        public object _getTriangleLock = new object();

        //64x64 each
        private volatile VertexPositionColorNormal[][] _vertexChunks;
        private int[][] _indicesChunks;
        private VertexPositionColor[] _vertexSelection;
        private int[] _indicesSelection;
        private volatile List<int> _drawnchunks = new List<int> { 0 };
        private int _currentChunk = 0;

        private Matrix _projection;
        private Matrix _camera;
        private Effect _effect;
        private Matrix _selectorWorld;
        private Vector3 _selectorPosition = Vector3.Zero;
        private volatile List<Vector3> _selectorPoints = new List<Vector3>();

        private Vector3 _cameraPos;

        private SpriteFont _verdana;

        private float _camXrot = 0;
        private float _camYrot = 0;
        private float _camViewDist = 40;

        public bool UseGamepad { get; set; }

        public float SelectorScale
        {
            get { return _selectorScale; }
            set
            {
                if (value >= 1 && value <= 20)
                    _selectorScale = value;
            }
        }

        private bool _allowMove = false;

        public static HeightmapEditor Instance { get; set; }

        public VertexPositionColorNormal[][] VertexChunks
        {
            get { return _vertexChunks; }
        }

        public int TerrainWidth
        {
            get { return _terrainWidth; }
        }

        public int TerrainHeight
        {
            get { return _terrainHeight; }
        }

        private int _terrainWidth = 0;
        private int _terrainHeight = 0;
        private float _selectorScale = 5;

        public HeightmapEditor()
        {
            UseGamepad = false;
            SelectorScale = 5f;
            _graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
            _keyboard_current = Keyboard.GetState();
            _mouse_current = Mouse.GetState();
            _gamepad_current = GamePad.GetState(PlayerIndex.One);
        }

        #region XNA Properties

        protected override void Initialize()
        {
            _graphics.PreferredBackBufferWidth = 800;
            _graphics.PreferredBackBufferHeight = 600;
            _graphics.IsFullScreen = false;
            _graphics.ApplyChanges();
            Window.Title = "Heightmap Editor";

            device = _graphics.GraphicsDevice;

            SetUpCamera();

            Instance = this;
            IsFixedTimeStep = false;
            IsMouseVisible = true;
            _spriteBatch = new SpriteBatch(device);
            GenerateSelector();
            _screenManager = new ScreenManager(this);
            base.Initialize();
        }

        protected override void LoadContent()
        {
            device = _graphics.GraphicsDevice;

            _effect = Content.Load<Effect>("HeightmapShader");
            _crosshair = Content.Load<Texture2D>("Crosshair");
            _verdana = Content.Load<SpriteFont>("Verdana");
            GenerateNew(2, 2);

            _screenManager.Initialize();
            _screenManager.AddScreen(new MainMenu(), PlayerIndex.One);
        }

        protected override void UnloadContent()
        {
        }

        #endregion

        public void MoveEnabling(bool enabled)
        {
            _allowMove = enabled;
        }

        public void Wireframe()
        {
            var rs = new RasterizerState();
            rs.CullMode = CullMode.None;
            rs.FillMode = FillMode.WireFrame;
            _graphics.GraphicsDevice.RasterizerState = rs;

            _effect.CurrentTechnique = _effect.Techniques["ColoredNoShading"];
        }

        public void Textured()
        {
            var rs = new RasterizerState();
            rs.CullMode = CullMode.None;
            rs.FillMode = FillMode.Solid;
            _graphics.GraphicsDevice.RasterizerState = rs;

            Vector3 lightDirection = new Vector3(1, -1, -1);
            lightDirection.Normalize();


            _effect.CurrentTechnique = _effect.Techniques["Colored"];
            _effect.Parameters["LightDirection"].SetValue(lightDirection);
            _effect.Parameters["Ambient"].SetValue(0.1f);
            _effect.Parameters["EnableLighting"].SetValue(true);
        }

        private void GenerateSelector()
        {
            _vertexSelection = new VertexPositionColor[12];
            _vertexSelection[0] = new VertexPositionColor(new Vector3(-1, 0, 0), Color.Red);
            _vertexSelection[1] = new VertexPositionColor(new Vector3(-0.86f, 0, 0.5f), Color.Red);
            _vertexSelection[2] = new VertexPositionColor(new Vector3(-0.5f, 0, 0.86f), Color.Red);
            _vertexSelection[3] = new VertexPositionColor(new Vector3(0, 0, 1), Color.Red);
            _vertexSelection[4] = new VertexPositionColor(new Vector3(0.5f, 0, 0.86f), Color.Red);
            _vertexSelection[5] = new VertexPositionColor(new Vector3(0.86f, 0, 0.5f), Color.Red);
            _vertexSelection[6] = new VertexPositionColor(new Vector3(1, 0, 0), Color.Red);
            _vertexSelection[7] = new VertexPositionColor(new Vector3(0.86f, 0, -0.5f), Color.Red);
            _vertexSelection[8] = new VertexPositionColor(new Vector3(0.5f, 0, -0.86f), Color.Red);
            _vertexSelection[9] = new VertexPositionColor(new Vector3(0, 0, -1), Color.Red);
            _vertexSelection[10] = new VertexPositionColor(new Vector3(-0.5f, 0, -0.86f), Color.Red);
            _vertexSelection[11] = new VertexPositionColor(new Vector3(-0.86f, 0, -0.5f), Color.Red);

            _indicesSelection = new[] { 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 0 };
        }

        public void GenerateNew(int resolutionX, int resolutionY)
        {
            _terrainHeight = resolutionY;
            _terrainWidth = resolutionX;

            SetUpVertices();
            SetUpIndices();
            SetUpCamera();
        }

        private void SetUpVertices()
        {
            _vertexChunks = new VertexPositionColorNormal[_terrainWidth * _terrainHeight][];
            for (int cx = 0; cx < _terrainWidth; cx++)
            {
                for (int cy = 0; cy < _terrainHeight; cy++)
                {
                    _vertexChunks[cx + cy * _terrainWidth] = new VertexPositionColorNormal[65 * 65];
                    //Chunk mode
                    for (int x = 0; x < 65; x++)
                    {
                        for (int y = 0; y < 65; y++)
                        {
                            _vertexChunks[cx + cy * _terrainWidth][x + y * 65].Position = new Vector3(cx * 64 + x, 0, -(cy * 64 + y));
                            _vertexChunks[cx + cy * _terrainWidth][x + y * 65].Color = Color.White;
                            _vertexChunks[cx + cy * _terrainWidth][x + y * 65].Normal = new Vector3(0);
                        }
                    }
                }
            }
        }

        private void SetUpIndices()
        {
            _indicesChunks = new int[_terrainWidth * _terrainHeight][];

            for (int cx = 0; cx < _terrainWidth; cx++)
            {
                for (int cy = 0; cy < _terrainHeight; cy++)
                {
                    //Chunk mode
                    _indicesChunks[cx + cy * _terrainWidth] = new int[(64) * (64) * 6];
                    uint counter = 0;

                    for (int y = 0; y < 64; y++)
                    {
                        for (int x = 0; x < 64; x++)
                        {
                            int topLeft = x + y * 65;
                            int topRight = (x + 1) + y * 65;
                            int lowerLeft = x + (y + 1) * 65;
                            int lowerRight = (x + 1) + (y + 1) * 65;

                            _indicesChunks[cx + cy * _terrainWidth][counter++] = lowerLeft;
                            _indicesChunks[cx + cy * _terrainWidth][counter++] = topRight;
                            _indicesChunks[cx + cy * _terrainWidth][counter++] = topLeft;

                            _indicesChunks[cx + cy * _terrainWidth][counter++] = lowerLeft;
                            _indicesChunks[cx + cy * _terrainWidth][counter++] = lowerRight;
                            _indicesChunks[cx + cy * _terrainWidth][counter++] = topRight;
                        }
                    }
                }
            }
        }

        private void SetUpCamera()
        {
            _cameraPos = new Vector3(_terrainWidth * 32, 0, -_terrainHeight * 32);
            _camViewDist = 40;
            _camXrot = 0;
            _camYrot = 0;
            _projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, device.Viewport.AspectRatio, 1.0f, 300.0f);
        }

        #region RayIntersection

        private void PickGamePadTriangleThread()
        {
            PickTriangleThread(new Vector2(_graphics.PreferredBackBufferWidth / 2, _graphics.PreferredBackBufferHeight / 2));
        }

        private void PickKeyboardTriangleThread()
        {
            PickTriangleThread(new Vector2(_mouse_current.X, _mouse_current.Y));
        }

        private void PickTriangleThread(Vector2 position)
        {

            Ray center = GetMouseCursorRay(position, _projection, _camera);
            float distance = 0;
            List<int> chunks = new List<int>();
            chunks.Add(_currentChunk - 1);
            chunks.Add(_currentChunk);
            chunks.Add(_currentChunk + 1);
            chunks.Add(_currentChunk - _terrainWidth - 1);
            chunks.Add(_currentChunk - _terrainWidth);
            chunks.Add(_currentChunk - _terrainWidth + 1);
            chunks.Add(_currentChunk + _terrainWidth - 1);
            chunks.Add(_currentChunk + _terrainWidth);
            chunks.Add(_currentChunk + _terrainWidth + 1);

            lock (_getTriangleLock)
            {
                PickedTriangle(center, ref _selectorPosition, out distance, chunks);

                _selectorWorld = Matrix.CreateScale(SelectorScale) *
                                 Matrix.CreateTranslation(_selectorPosition);

                _selectorPoints = GetVerticesAround(new Vector2(_selectorPosition.X, _selectorPosition.Z),
                                                    _selectorScale);
            }
        }

        public bool PickedTriangle(Ray cursorRay, ref Vector3 intersection, out float distance, List<int> chunks)
        {
            distance = float.PositiveInfinity;
            float pickDist = 0.0f; // distance from ray origin to intersection
            float pickU = 0.0f; // barycentric coordinate of intersection
            float pickV = 0.0f; // barycentric coordinate of intersection

            for (int chunk = 0; chunk < chunks.Count; chunk++)
            {
                if (chunks[chunk] < 0 || chunks[chunk] >= _terrainWidth * _terrainHeight)
                    continue;

                for (int i = 0; i < _indicesChunks[chunks[chunk]].Length; i += 3)
                {
                    // changed from left handed (DirectX) to right handed (XNA)
                    Vector3 tri0 = _vertexChunks[chunks[chunk]][_indicesChunks[chunks[chunk]][i + 0]].Position;
                    Vector3 tri1 = _vertexChunks[chunks[chunk]][_indicesChunks[chunks[chunk]][i + 2]].Position;
                    Vector3 tri2 = _vertexChunks[chunks[chunk]][_indicesChunks[chunks[chunk]][i + 1]].Position;

                    // Check if the pick ray passes through this point
                    if (RayIntersectTriangle(cursorRay.Position, cursorRay.Direction, tri0, tri1, tri2, ref pickDist,
                                             ref pickU, ref pickV))
                    {
                        if (pickDist > 0.0f && pickDist < distance)
                        {
                            intersection = pickU * tri1 + pickV * tri2 + (1 - pickU - pickV) * tri0;
                            distance = pickDist;
                        }
                    }
                }
            }

            if (pickDist > 0f)
                return true;

            return false;
        }

        private bool RayIntersectTriangle(Vector3 rayPosition, Vector3 rayDirection, Vector3 tri0, Vector3 tri1, Vector3 tri2, ref float pickDistance, ref float barycentricU, ref float barycentricV)
        {
            // Find vectors for two edges sharing vert0
            Vector3 edge1 = tri1 - tri0;
            Vector3 edge2 = tri2 - tri0;

            // Begin calculating determinant - also used to calculate barycentricU parameter
            Vector3 pvec = Vector3.Cross(rayDirection, edge2);

            // If determinant is near zero, ray lies in plane of triangle
            float det = Vector3.Dot(edge1, pvec);
            if (det < 0.0001f)
                return false;

            // Calculate distance from vert0 to ray origin
            Vector3 tvec = rayPosition - tri0;

            // Calculate barycentricU parameter and test bounds
            barycentricU = Vector3.Dot(tvec, pvec);
            if (barycentricU < 0.0f || barycentricU > det)
                return false;

            // Prepare to test barycentricV parameter
            Vector3 qvec = Vector3.Cross(tvec, edge1);

            // Calculate barycentricV parameter and test bounds
            barycentricV = Vector3.Dot(rayDirection, qvec);
            if (barycentricV < 0.0f || barycentricU + barycentricV > det)
                return false;

            // Calculate pickDistance, scale parameters, ray intersects triangle
            pickDistance = Vector3.Dot(edge2, qvec);
            float fInvDet = 1.0f / det;
            pickDistance *= fInvDet;
            barycentricU *= fInvDet;
            barycentricV *= fInvDet;

            return true;
        }

        public Ray GetMouseCursorRay(Vector2 screenPosition, Matrix projectionMatrix, Matrix viewMatrix)
        {
            // Create 2 positions in screenspace using the cursor position.
            // 0 is as close as possible to the camera, 1 is as far away as possible.
            Vector3 nearSource = new Vector3(screenPosition.X, screenPosition.Y, 0.0f);
            Vector3 farSource = new Vector3(screenPosition.X, screenPosition.Y, 1.0f);

            // Use Viewport. Unproject to tell what those two screen space positions would be in world space. 
            // We'll need the projection matrix and view matrix, which we have saved as member variables. 
            // We also need a world matrix, which can just be identity.
            Vector3 nearPoint = device.Viewport.Unproject(nearSource, projectionMatrix, viewMatrix, Matrix.Identity);
            Vector3 farPoint = device.Viewport.Unproject(farSource, projectionMatrix, viewMatrix, Matrix.Identity);

            // Find the direction vector that goes from the nearPoint to the farPoint and normalize it...
            Vector3 direction = farPoint - nearPoint;
            direction.Normalize();

            // ... and then create a new ray using nearPoint as the source.
            return new Ray(nearPoint, direction);
        }
        #endregion

        private int GetChunk(int x, int y)
        {
            int chunkX = x / 64;
            int chunkY = -y / 64;
            if ((chunkX + chunkY * _terrainWidth) < _vertexChunks.Length)
                return (chunkX + chunkY * _terrainWidth);

            if (chunkX > _terrainWidth - 1)
                chunkX = _terrainWidth - 1;
            if (chunkY > _terrainHeight - 1)
                chunkY = _terrainHeight - 1;

            return (chunkX + chunkY * _terrainWidth);
        }

        private Vector3 GetChunk3(int x, int y)
        {
            int chunkX = x / 64;
            int chunkY = -y / 64;

            if (chunkX < _terrainWidth && chunkY < _terrainHeight)
                return new Vector3(chunkX, chunkY, chunkX + chunkY * _terrainWidth);

            return new Vector3(-1, -1, -1);
        }

        private List<Vector3> GetVerticesAround(Vector2 position, float radius)
        {
            List<Vector3> returnVal = new List<Vector3>();
            Vector3 chunk;
            int index;
            float distance;
            Vector3 result;
            Vector2 chunkedPosition;
            bool flipX, flipY;
            //Check all points to unit circle using a rectangular box
            for (int y = (int)(position.Y + radius); y > position.Y - radius; y--)
            {
                if (y > 0 || y < -_terrainHeight * 64)
                    continue;

                for (int x = (int)(position.X - radius); x < position.X + radius; x++)
                {
                    if (x < 0 || x > _terrainWidth * 64)
                        continue;

                    //if posX^2 + posY^2 < 1 reffered to unit circle, point is contained
                    if (((y - position.Y) / radius) * ((y - position.Y) / radius) + ((x - position.X) / radius) * ((x - position.X) / radius) > 1)
                        continue;

                    distance = (float)Math.Sqrt((x - position.X) * (x - position.X) + (y - position.Y) * (y - position.Y));
                    chunk = GetChunk3(x, y);

                    if (chunk.Z < 0 || chunk.Z >= _terrainWidth * _terrainHeight)
                        continue;

                    chunkedPosition = new Vector2((x - chunk.X * 64), (-y - chunk.Y * 64));
                    result = new Vector3(chunk.Z, chunkedPosition.X + chunkedPosition.Y * 65, distance);
                    if (result.Y >= 0 && result.Y < 65 * 65)
                    {
                        returnVal.Add(result);

                        flipX = false;
                        flipY = false;

                        if (chunk.X > 0 && chunkedPosition.X == 0)
                        {
                            returnVal.Add(new Vector3(chunk.Z - 1, 64 + chunkedPosition.Y * 65, distance));
                            flipX = true;
                        }
                        if (chunk.Y > 0 && chunkedPosition.Y == 0)
                        {
                            returnVal.Add(new Vector3(chunk.Z - _terrainWidth, chunkedPosition.X + 64 * 65, distance));
                            flipY = true;
                        }
                        if (flipX && flipY)
                        {
                            returnVal.Add(new Vector3(chunk.Z - (_terrainWidth + 1), 65 * 65 - 1, distance));
                        }
                    }
                }
            }

            return returnVal;
        }

        private void RecalculateNormals()
        {
            for (float x = -_selectorScale - 1; x <= +_selectorScale + 1; x++)
            {
                for (float y = -_selectorScale - 1; y <= +_selectorScale + 1; y++)
                {
                    Vector3 chunk = GetChunk3((int)(x + _selectorPosition.X), (int)(y + _selectorPosition.Z));
                    int vertex = ((int)(x + _selectorPosition.X) - (int)chunk.X * 64) +
                                 ((int)(y + _selectorPosition.Z) - (int)chunk.Y * 64) * 65;

                    _vertexChunks[(int)chunk.Z][vertex].Normal = Vector3.Zero;
                }
            }

            for (float x = -_selectorScale - 2; x < +_selectorScale + 2; x++)
            {
                for (float y = -_selectorScale - 2; y < +_selectorScale + 2; y++)
                {
                    Vector3 chunk = GetChunk3((int)(x + _selectorPosition.X), (int)(y + _selectorPosition.Z));
                    int vertex = ((int)(x + _selectorPosition.X) - (int)chunk.X * 64) +
                                 ((int)(y + _selectorPosition.Z) - (int)chunk.Y * 64) * 65;


                }
            }

            for (float x = -_selectorScale - 1; x <= +_selectorScale + 1; x++)
            {
                for (float y = -_selectorScale - 1; y <= +_selectorScale + 1; y++)
                {
                    Vector3 chunk = GetChunk3((int)(x + _selectorPosition.X), (int)(y + _selectorPosition.Z));
                    int vertex = ((int)(x + _selectorPosition.X) - (int)chunk.X * 64) +
                                 ((int)(y + _selectorPosition.Z) - (int)chunk.Y * 64) * 65;

                    _vertexChunks[(int)chunk.Z][vertex].Normal.Normalize();
                }
            }
        }

        protected override void Update(GameTime gameTime)
        {
            var camMove = new Vector3();

            _currentChunk = GetChunk((int)_cameraPos.X, (int)_cameraPos.Z);


            if (!_screenManager.GetScreens().Any())
            {
                _allowMove = true;


                #region Gamepad

                if (UseGamepad)
                {
                    _gamepad_old = _gamepad_current;
                    _gamepad_current = GamePad.GetState(PlayerIndex.One);

                    #region Movement

                    //Check menu
                    if (_gamepad_current.Buttons.Start == ButtonState.Pressed)
                    {
                        _screenManager.AddScreen(new MainMenu(), PlayerIndex.One);
                        _allowMove = false;
                    }
                    if (_gamepad_current.Buttons.Back == ButtonState.Pressed)
                    {
                        _screenManager.AddScreen(new Settings(), PlayerIndex.One);
                        _allowMove = false;
                    }

                    //movement
                    if (Math.Abs(_gamepad_current.ThumbSticks.Right.Y) > 0.01f)
                        _camXrot += MathHelper.ToRadians(_gamepad_current.ThumbSticks.Right.Y) *
                                    (float)gameTime.ElapsedGameTime.TotalMilliseconds * 0.2f;
                    if (Math.Abs(_gamepad_current.ThumbSticks.Right.X) > 0.01f)
                        _camYrot -= MathHelper.ToRadians(_gamepad_current.ThumbSticks.Right.X) *
                                    (float)gameTime.ElapsedGameTime.TotalMilliseconds * 0.1f;

                    if (_gamepad_current.Buttons.LeftShoulder == ButtonState.Pressed)
                        _camViewDist -= (float)gameTime.ElapsedGameTime.TotalMilliseconds * 0.1f;
                    else if (_gamepad_current.Buttons.RightShoulder == ButtonState.Pressed)
                        _camViewDist += (float)gameTime.ElapsedGameTime.TotalMilliseconds * 0.1f;

                    if (Math.Abs(_gamepad_current.ThumbSticks.Left.X) > 0.01)
                        camMove.X = -_gamepad_current.ThumbSticks.Left.X * (float)gameTime.ElapsedGameTime.TotalMilliseconds * 0.2f *
                                    _camViewDist * 0.01f;
                    if (Math.Abs(_gamepad_current.ThumbSticks.Left.Y) > 0.01)
                        camMove.Z = _gamepad_current.ThumbSticks.Left.Y * (float)gameTime.ElapsedGameTime.TotalMilliseconds * 0.2f *
                                    _camViewDist * 0.01f;
                    #endregion

                    #region Settings
                    if (_gamepad_current.DPad.Up == ButtonState.Pressed && _gamepad_old.DPad.Up == ButtonState.Released)
                        SelectorScale += 0.5f;
                    if (_gamepad_current.DPad.Down == ButtonState.Pressed && _gamepad_old.DPad.Down == ButtonState.Released)
                        SelectorScale -= 0.5f;
                    #endregion

                    #region Terrain
                    //Elevate with triggers
                    if (_gamepad_current.Triggers.Left > 0.05f)
                    {
                        foreach (Vector3 point in _selectorPoints)
                        {
                            _vertexChunks[(int)point.X][(int)point.Y].Position.Y += (_selectorScale - point.Z) /
                                                                                      _selectorScale * _gamepad_current.Triggers.Left *
                                                                                      (float)
                                                                                      gameTime.ElapsedGameTime.
                                                                                          TotalSeconds * 8f;
                        }
                        RecalculateNormals();
                    }
                    else if (_gamepad_current.Triggers.Right > 0.05f)
                    {
                        foreach (Vector3 point in _selectorPoints)
                        {
                            _vertexChunks[(int)point.X][(int)point.Y].Position.Y -= (_selectorScale - point.Z) /
                                                                                      _selectorScale *
                                                                                      _gamepad_current.Triggers.Right *
                                                                                      (float)
                                                                                      gameTime.ElapsedGameTime.
                                                                                          TotalSeconds * 8f;
                        }
                        RecalculateNormals();
                    }
                    #endregion
                    //Get picked triangle

                    var selector = new Thread(PickGamePadTriangleThread);
                    selector.Start();

                #endregion
                }
                #region Keyboard

                else
                {
                    _keyboard_old = _keyboard_current;
                    _keyboard_current = Keyboard.GetState();
                    _mouse_old = _mouse_current;
                    _mouse_current = Mouse.GetState();

                    #region Movement
                    //Check menu
                    if (_keyboard_current.IsKeyDown(Keys.Tab) && _keyboard_old.IsKeyUp(Keys.Tab))
                    {
                        _screenManager.AddScreen(new MainMenu(), PlayerIndex.One);
                        _allowMove = false;
                    }
                    else if (_keyboard_current.IsKeyDown(Keys.R) && _keyboard_old.IsKeyUp(Keys.R))
                    {
                        _screenManager.AddScreen(new Settings(), PlayerIndex.One);
                        _allowMove = false;
                    }

                    //Camera axis
                    _camXrot -= (_mouse_current.MiddleButton == ButtonState.Pressed
                                     ? (_mouse_old.Y - _mouse_current.Y) * (float)gameTime.ElapsedGameTime.TotalSeconds * 0.2f
                                     : 0);
                    _camYrot += (_mouse_current.MiddleButton == ButtonState.Pressed
                                     ? (_mouse_old.X - _mouse_current.X) * (float)gameTime.ElapsedGameTime.TotalSeconds * 0.6f
                                     : 0);


                    //Zoom
                    if (_keyboard_current.IsKeyUp(Keys.LeftShift))
                        _camViewDist += (float)gameTime.ElapsedGameTime.TotalSeconds * 2.2f * (_mouse_current.ScrollWheelValue - _mouse_old.ScrollWheelValue);

                    //Camera movement
                    if (_keyboard_current.IsKeyDown(Keys.W))
                        camMove.Z = (float)gameTime.ElapsedGameTime.TotalMilliseconds * 0.2f * _camViewDist * 0.01f;
                    else if (_keyboard_current.IsKeyDown(Keys.S))
                        camMove.Z = -(float)gameTime.ElapsedGameTime.TotalMilliseconds * 0.2f * _camViewDist * 0.01f;
                    if (_keyboard_current.IsKeyDown(Keys.A))
                        camMove.X = (float)gameTime.ElapsedGameTime.TotalMilliseconds * 0.2f * _camViewDist * 0.01f;
                    else if (_keyboard_current.IsKeyDown(Keys.D))
                        camMove.X = -(float)gameTime.ElapsedGameTime.TotalMilliseconds * 0.2f * _camViewDist * 0.01f;
                    #endregion

                    #region Settings
                    if (_keyboard_current.IsKeyDown(Keys.LeftShift))
                        SelectorScale += (float)gameTime.ElapsedGameTime.TotalSeconds *
                                         (_mouse_current.ScrollWheelValue - _mouse_old.ScrollWheelValue);
                    #endregion

                    #region Terrain
                    //Elevate with triggers
                    if (_mouse_current.LeftButton == ButtonState.Pressed)
                    {
                        foreach (Vector3 point in _selectorPoints)
                        {
                            _vertexChunks[(int)point.X][(int)point.Y].Position.Y += (_selectorScale - point.Z) /
                                                                                      _selectorScale *
                                                                                      (float)
                                                                                      gameTime.ElapsedGameTime.
                                                                                          TotalSeconds * 8f;
                        }
                    }
                    else if (_mouse_current.RightButton == ButtonState.Pressed)
                    {
                        foreach (Vector3 point in _selectorPoints)
                        {
                            _vertexChunks[(int)point.X][(int)point.Y].Position.Y -= (_selectorScale - point.Z) /
                                                                                      _selectorScale *
                                                                                      (float)
                                                                                      gameTime.ElapsedGameTime.
                                                                                          TotalSeconds * 8f;
                        }
                    }
                    #endregion

                    var selector = new Thread(PickKeyboardTriangleThread);
                    selector.Start();
                }

                #endregion
            }

            if (_camViewDist < 15) _camViewDist = 15;
            else if (_camViewDist > 70) _camViewDist = 70;

            if (MathHelper.ToDegrees(_camXrot) < 5)
                _camXrot = MathHelper.ToRadians(5);
            else if (MathHelper.ToDegrees(_camXrot) > 70)
                _camXrot = MathHelper.ToRadians(70);

            var cameraRot = Matrix.CreateRotationX(_camXrot) * Matrix.CreateRotationY(_camYrot);
            var cameraYRot = Matrix.CreateRotationY(_camYrot);
            _cameraPos += Vector3.Transform(camMove, cameraYRot);

            if (_cameraPos.X < 0) _cameraPos.X = 0;
            else if (_cameraPos.X > _terrainWidth * 64) _cameraPos.X = _terrainWidth * 64;
            if (_cameraPos.Z > 0) _cameraPos.Z = 0;
            else if (_cameraPos.Z < -_terrainHeight * 64) _cameraPos.Z = -_terrainHeight * 64;

            var cameraLookAt = new Vector3(0, 0, _camViewDist);
            _camera = Matrix.CreateLookAt(_cameraPos - Vector3.Transform(cameraLookAt, cameraRot), _cameraPos,
                                          Vector3.Up);



            _screenManager.Update(gameTime);

            _drawnchunks.Clear();

            for (int y = -2; y <= 2; y++)
            {
                for (int x = -2; x <= 2; x++)
                {
                    int actual = _currentChunk + _terrainWidth * y + x;

                    if (actual >= 0 && actual < _vertexChunks.Length)
                        _drawnchunks.Add(_currentChunk + _terrainWidth * y + x);
                }
            }

            //switch between keyboard and gamepad
            if (GamePad.GetState(PlayerIndex.One).Buttons.LeftStick == ButtonState.Pressed)
                UseGamepad = true;
            else if (Keyboard.GetState().IsKeyDown(Keys.F4))
                UseGamepad = false;

            base.Update(gameTime);
        }

        protected override void Draw(GameTime gameTime)
        {
            device.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1.0f, 0);

            Wireframe();

            _effect.Parameters["View"].SetValue(_camera);
            _effect.Parameters["Projection"].SetValue(_projection);
            _effect.Parameters["World"].SetValue(Matrix.CreateTranslation(0, 0, 0));

            foreach (EffectPass pass in _effect.CurrentTechnique.Passes)
            {
                pass.Apply();

                try
                {
                    for (int i = 0; i < _drawnchunks.Count; i++)
                    {
                        device.DrawUserIndexedPrimitives(PrimitiveType.TriangleList, _vertexChunks[_drawnchunks[i]], 0,
                                                         _vertexChunks[_drawnchunks[i]].Length,
                                                         _indicesChunks[_drawnchunks[i]], 0, _indicesChunks[_drawnchunks[i]].Length / 3, VertexPositionColorNormal.VertexDeclaration);
                    }
                }
                catch (Exception) { }

            }

            Wireframe();
            _effect.Parameters["World"].SetValue(_selectorWorld);
            foreach (EffectPass pass in _effect.CurrentTechnique.Passes)
            {
                pass.Apply();
                device.DrawUserIndexedPrimitives(PrimitiveType.LineList, _vertexSelection, 0, _vertexSelection.Length, _indicesSelection, 0, _indicesSelection.Length / 2);
            }

            _spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend);
            _spriteBatch.Draw(_crosshair, new Vector2(_graphics.PreferredBackBufferWidth / 2 - 16f,
                                                      _graphics.PreferredBackBufferHeight / 2 - 16f), Color.White);
            _spriteBatch.DrawString(_verdana, "Input Mode: " + (UseGamepad ? "GamePad" : "Keyboard"), new Vector2(20, 20), Color.Orange);
            _spriteBatch.End();

            if (!_allowMove)
            {

                _screenManager.FadeBackBufferToBlack();
                _screenManager.Draw(gameTime);
            }

            base.Draw(gameTime);
        }
    }
}